-
-
Notifications
You must be signed in to change notification settings - Fork 19
Enhance UI/UX of time-tracker #3101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add assignee information to tasks API with is_assigned_to_current_user flag - Implement smart task sorting: assigned tasks → priority → creation date - Add 'My Tasks' and 'Unassigned' filters with count badges - Add filter persistence using localStorage with workspace-specific keys - Enhance task cards with assignee avatars and assignment status - Add visual indicators for user-assigned tasks (blue styling) - Implement active filter chips with individual remove functionality - Add feature parity between sidebar and timer controls filtering"
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
Caution Review failedThe pull request is closed. WalkthroughThis update introduces a comprehensive overhaul to the time tracking and task management features. It adds new timer modes (Pomodoro, Custom, enhanced Stopwatch), persistent pause/resume logic, richer analytics and focus scoring, advanced session filtering, and multiple new visualization modes for activity heatmaps. Types are centralized, API responses are enriched, and database schema is updated for session resumption tracking. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant TimerControls
participant LocalStorage
participant API
participant UI
User->>TimerControls: Select timer mode (Stopwatch/Pomodoro/Custom)
TimerControls->>UI: Show timer mode UI and settings
User->>TimerControls: Start timer
TimerControls->>API: Create new session
TimerControls->>UI: Display timer countdown/progress
User->>TimerControls: Pause timer
TimerControls->>LocalStorage: Save paused session state
User->>TimerControls: Resume timer
TimerControls->>LocalStorage: Restore paused session state
TimerControls->>UI: Resume timer display
User->>TimerControls: Stop timer
TimerControls->>API: Finalize session
TimerControls->>UI: Update analytics and summaries
sequenceDiagram
participant User
participant ActivityHeatmap
participant LocalStorage
User->>ActivityHeatmap: Open heatmap
ActivityHeatmap->>LocalStorage: Load settings and onboarding state
ActivityHeatmap->>User: Show selected view mode (original/hybrid/calendar/cards)
User->>ActivityHeatmap: Change view mode or settings
ActivityHeatmap->>LocalStorage: Save updated settings
ActivityHeatmap->>User: Show onboarding tips if applicable
Possibly related PRs
Suggested labels
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
packages/types/src/supabase.tsOops! Something went wrong! :( ESLint: 9.29.0 Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@typescript-eslint/parser' imported from /eslint.config.mjs 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
✨ Finishing Touches
🧪 Generate Unit Tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @Adinorio, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly enhances the time tracker's task management UI/UX. It introduces assignee information into the task data, implements a smarter sorting mechanism that prioritizes assigned tasks, and adds new filtering capabilities for 'My Tasks' and 'Unassigned' tasks. The filters are now persistent, and the UI provides clearer visual cues for assigned tasks and active filters, improving usability and task discoverability.
Highlights
- Assignee Information: Tasks API now includes assignee details and a flag (
is_assigned_to_current_user
) indicating if the current user is assigned. - Task Sorting: Implemented a new sorting logic for tasks: assigned tasks are prioritized first, followed by priority (highest first), and then creation date (newest first).
- Assignee Filters: Added 'My Tasks' and 'Unassigned' quick filters to both the timer controls dropdown and the sidebar, including counts for each category.
- Filter Persistence: Task filters (board, list, assignee) are now persisted in
localStorage
per workspace, so they are remembered between sessions. - Enhanced Task Cards: Task cards in both the timer controls dropdown and the sidebar now display assignee avatars and include visual indicators (blue styling, 'Assigned to you' badge) for tasks assigned to the current user.
- Active Filter Display: Active filters (search, board, list, assignee) are now displayed as removable chips in the sidebar, with a 'Clear all' option.
- Search Enhancement: Task search now includes the task description in addition to the name.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configureGemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request enhances the time-tracker's UI/UX. My review highlights opportunities to improve maintainability by reducing code duplication, enhance type safety in API data handling, and potentially optimize performance. Addressing these points will make the codebase more robust.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/time-tracker-content.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx
Outdated
Show resolved
Hide resolved
{(() => { | ||
const myTasksCount = tasks.filter( | ||
(task) => task.is_assigned_to_current_user | ||
).length; | ||
return myTasksCount > 0 ? ( | ||
<span className="ml-1 rounded-full bg-current px-1.5 py-0.5 text-[10px] text-white"> | ||
{myTasksCount} | ||
</span> | ||
) : null; | ||
})()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/time-tracker-content.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/time-tracker-content.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/time-tracker-content.tsx
Outdated
Show resolved
Hide resolved
- Add assignee information to tasks API endpoint - Implement 'My Tasks' and 'Unassigned' quick filters with count badges - Add filter state persistence using localStorage - Enhance task sorting to prioritize user's assigned tasks - Add visual indicators for assigned tasks (blue styling, badges) - Display assignee avatars in task cards - Ensure feature parity between sidebar and timer controls - Fix TypeScript linter errors"
- hide dragging of tasks tab when viewing other's tracked time
- Add comprehensive session analytics with focus score calculation - Implement advanced filtering (duration, productivity, time-of-day, quality) - Add real-time productivity insights and AI coaching suggestions - Fix task assignment API errors by using direct Supabase operations - Improve Quick Actions with live focus scores and smart timer suggestions - Disable Continue Last Session card when timer is already running - Add Next Task preview with priority-based smart selection - Enhance session history with productivity type classification - Update command center with real-time metrics and streak tracking"
- fixed issues
- import, incorrect types, mismatch, and undefined errors
…-v2' into feat/time-tracker/ux-improvement-v2
…-v2' into feat/time-tracker/ux-improvement-v2
- Remove disabled state from dismiss button when external settings provided - Add intelligent display logic based on user engagement and timing - Implement localStorage persistence for dismiss preferences - Add auto-hide functionality after 45 seconds for experienced users - Include contextual tips that adapt to current heatmap view mode - Add cross-component event system for settings synchronization - Enhance UX with proper hover states, tooltips, and accessibility - Fix TypeScript compilation errors in compact cards component - Ensure tips reappear strategically (every 7 days or view mode changes) - Prevent annoying repetition while maintaining helpful onboarding flow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🔭 Outside diff range comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx (1)
8-17
:params
type is incorrect – breaks Next.js typing & causes unnecessaryawait
params
is synchronously supplied by the framework, not aPromise
. Declaring it asPromise<…>
forces the awkwardawait params
and may break type-checking / IDE hints.-interface Props { - params: Promise<{ - locale: string; - wsId: string; - }>; -} +interface Props { + params: { + locale: string; + wsId: string; + }; +} export default async function TimeTrackerPage({ params }: Props) { - const { wsId } = await params; + const { wsId } = params;apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)
1-1507
: Break down this large component into smaller, focused componentsThis component is over 1500 lines and handles multiple responsibilities. It should be decomposed into smaller, more manageable components.
Consider breaking this component into:
- ActivityHeatmap.tsx - Main container component (200-300 lines)
- HeatmapViews/ - Directory for view components:
- OriginalGridView.tsx - GitHub-style grid view
- HybridView.tsx - Combined year/month view
- CalendarView.tsx - Monthly calendar
- CompactCardsView.tsx - Card-based view
- HeatmapControls.tsx - Settings dropdown and legend
- OnboardingTips.tsx - Onboarding tips component
- useHeatmapSettings.ts - Custom hook for settings management
- useOnboardingState.ts - Custom hook for onboarding state
This would improve:
- Maintainability and readability
- Test coverage (easier to test smaller components)
- Performance (better code splitting)
- Team collaboration (less merge conflicts)
Would you like me to help create this refactored structure?
♻️ Duplicate comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)
106-136
:any
usage & quadratic de-duplication hinder type-safety and scalability
The transformation pipeline still relies on
any
(task: any
,a: any
,user: any
).
This repeats the concern raised in earlier reviews – consider defining aDbTaskRow
interface to capture the row shape returned by Supabase and keep the mapper strictly typed.De-duplicating assignees with
self.findIndex((u) => u.id === user.id) === indexis O(n²). A single
Set
gives linear complexity and clearer intent:- const seen = new Set<string>() - const assignees = task.assignees - ?.map((t) => t.user) - ?.filter((u) => u?.id && !seen.has(u.id) && seen.add(u.id)) + const seen = new Set<string>(); + const assignees = + task.assignees + ?.map((t) => t.user) + ?.filter((u): u is { id: string } => { + if (!u?.id || seen.has(u.id)) return false; + seen.add(u.id); + return true; + }) ?? [];This also removes the optional-chaining warning reported by Biome.
🧹 Nitpick comments (17)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (2)
4-19
: Clarify time-unit semantics inTimerStats
The interface mixes genericnumber
fields without indicating whether they represent seconds, minutes, or milliseconds. Consumers must read the implementation to guess. Explicitly documenting (or renaming) these fields avoids silent unit-conversion bugs.export interface TimerStats { - todayTime: number; - weekTime: number; - monthTime: number; + /** time in seconds */ + todayTime: number; + /** time in seconds */ + weekTime: number; + /** time in seconds */ + monthTime: number;
55-68
: Filters default to empty strings – considernull | undefined
Using empty strings to denote “no filter” forces downstream comparisons (if (filter.board)
vsif (filter.board !== '')
). Allowingundefined
is more idiomatic and avoids hidden truthy / falsy traps.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx (1)
34-44
: Minor: redundant data mapping
rawData
already matchesTimeTrackerData
; the spread could replace the manual copy:-const initialData: TimeTrackerData = { - categories: rawData.categories, - runningSession: rawData.runningSession, - recentSessions: rawData.recentSessions, - goals: rawData.goals, - tasks: rawData.tasks, - stats: rawData.stats, -}; +const initialData: TimeTrackerData = { ...rawData };apps/web/src/components/command/quick-actions.tsx (1)
19-22
:isPeakHour
recalculates every render & uses hard-coded slots
While cheap, moving this logic touseMemo
(or a util) avoids recomputation and improves testability. Config-driven peak-hour windows would also decouple UI from business rules.apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)
93-101
: Minor: redundant null-check –data
is nevernull
on successful Supabase calls
supabase.from(...).select(...).range(...)
returnsdata
as an empty array, notnull
, when no rows match.
The additionalif (!data)
branch is therefore unreachable and can be removed to simplify the control-flow.apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (1)
1117-1118
: Style overrides defeatvariant
semantics; consider custom variant insteadEach of these buttons sets
variant="outline"
/"destructive"
and then overrides background, border, and text classes directly.
The resulting DOM mixes two styling sources, increasing CSS specificity and maintenance cost. If the default variants are insufficient, exposing a dedicated “muted” or “accent-outline” variant in the design-system would keep styling declarative:-<Button - className="w-full bg-muted hover:bg-muted/80 text-foreground border border-border hover:border-accent ..." - variant="outline" -> +<Button variant="muted" className="w-full">Also applies to: 1295-1296, 1962-1963
apps/web/src/components/command/quick-time-tracker.tsx (2)
380-393
: Simplify progress bar color and width logicThe nested ternary operators for determining color classes and calculating width can be simplified using a mapping approach.
+const getProgressBarColor = (seconds: number): string => { + const colorMap = [ + { threshold: 7200, color: "bg-green-500 dark:bg-green-600" }, + { threshold: 3600, color: "bg-blue-500 dark:bg-blue-600" }, + { threshold: 1800, color: "bg-yellow-500 dark:bg-yellow-600" }, + { threshold: 0, color: "bg-gray-500 dark:bg-gray-600" } + ]; + + return colorMap.find(({ threshold }) => seconds >= threshold)?.color || colorMap[colorMap.length - 1].color; +}; <div className={cn( "h-1.5 w-16 rounded-full", - elapsedTime >= 7200 ? "bg-green-500 dark:bg-green-600" : - elapsedTime >= 3600 ? "bg-blue-500 dark:bg-blue-600" : - elapsedTime >= 1800 ? "bg-yellow-500 dark:bg-yellow-600" : "bg-gray-500 dark:bg-gray-600" + getProgressBarColor(elapsedTime) )}>
429-446
: Extract session type logic for better readabilityThe session type determination uses multiple nested ternary operators which reduces readability.
+interface SessionType { + icon: string; + label: string; + colorClasses: string; +} + +const getSessionType = (elapsedSeconds: number): SessionType => { + const types: Array<{ threshold: number; type: SessionType }> = [ + { + threshold: 3600, + type: { + icon: '🧠', + label: 'Deep Work', + colorClasses: "text-green-700 bg-green-100 dark:text-green-300 dark:bg-green-950/30" + } + }, + { + threshold: 1800, + type: { + icon: '🎯', + label: 'Focused', + colorClasses: "text-blue-700 bg-blue-100 dark:text-blue-300 dark:bg-blue-950/30" + } + }, + { + threshold: 900, + type: { + icon: '📋', + label: 'Standard', + colorClasses: "text-yellow-700 bg-yellow-100 dark:text-yellow-300 dark:bg-yellow-950/30" + } + }, + { + threshold: 0, + type: { + icon: '⚡', + label: 'Quick Task', + colorClasses: "text-gray-700 bg-gray-100 dark:text-gray-300 dark:bg-gray-950/30" + } + } + ]; + + const sessionType = types.find(({ threshold }) => elapsedSeconds >= threshold)?.type || types[types.length - 1].type; + return sessionType; +}; +const sessionType = getSessionType(elapsedTime); <div className={cn( "flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium", - elapsedTime >= 3600 ? "text-green-700 bg-green-100 dark:text-green-300 dark:bg-green-950/30" : - elapsedTime >= 1800 ? "text-blue-700 bg-blue-100 dark:text-blue-300 dark:bg-blue-950/30" : - elapsedTime >= 900 ? "text-yellow-700 bg-yellow-100 dark:text-yellow-300 dark:bg-yellow-950/30" : - "text-gray-700 bg-gray-100 dark:text-gray-300 dark:bg-gray-950/30" + sessionType.colorClasses )}> - <span> - {elapsedTime >= 3600 ? '🧠' : - elapsedTime >= 1800 ? '🎯' : - elapsedTime >= 900 ? '📋' : '⚡'} - </span> - <span> - {elapsedTime >= 3600 ? 'Deep Work' : - elapsedTime >= 1800 ? 'Focused' : - elapsedTime >= 900 ? 'Standard' : 'Quick Task'} - </span> + <span>{sessionType.icon}</span> + <span>{sessionType.label}</span> </div>apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (3)
24-28
: Consider null safety for priority valuesThe priority comparison assumes numeric values but doesn't validate them. Consider adding null/undefined checks.
// Then sort by priority (higher priority first) -const aPriority = a.priority || 0; -const bPriority = b.priority || 0; +const aPriority = Number(a.priority) || 0; +const bPriority = Number(b.priority) || 0; if (aPriority !== bPriority) { return bPriority - aPriority; }
40-129
: Consider consolidating filter functions to reduce duplicationThe
filterTasksForTimer
andfilterTasksForSidebar
functions share similar filtering logic. Consider extracting common logic to reduce duplication.+interface BaseFilters { + board: string; + list: string; + assignee: string; +} + +function applyCommonFilters( + task: ExtendedWorkspaceTask, + searchQuery: string, + filters: BaseFilters +): boolean { + // Search filter + const matchesSearch = !searchQuery || + task.name?.toLowerCase().includes(searchQuery.toLowerCase()) || + task.description?.toLowerCase().includes(searchQuery.toLowerCase()); + + if (!matchesSearch) return false; + + // Board filter + if (filters.board && filters.board !== 'all' && task.board_name !== filters.board) { + return false; + } + + // List filter + if (filters.list && filters.list !== 'all' && task.list_name !== filters.list) { + return false; + } + + // Assignee filter + if (filters.assignee === 'mine') { + return task.is_assigned_to_current_user === true; + } else if (filters.assignee === 'unassigned') { + return !task.assignees || task.assignees.length === 0; + } else if (filters.assignee && filters.assignee !== 'all') { + return task.assignees?.some(assignee => assignee.id === filters.assignee) ?? false; + } + + return true; +} export function filterTasksForTimer( tasks: ExtendedWorkspaceTask[], searchQuery: string, filters: TaskFilters ): ExtendedWorkspaceTask[] { return tasks.filter((task) => { - const matchesSearch = - task.name?.toLowerCase().includes(searchQuery.toLowerCase()) || - task.description?.toLowerCase().includes(searchQuery.toLowerCase()); + if (!applyCommonFilters(task, searchQuery, filters)) return false; const matchesPriority = filters.priority === 'all' || String(task.priority) === filters.priority; const matchesStatus = filters.status === 'all' || (task.completed ? 'completed' : 'active') === filters.status; - const matchesBoard = - filters.board === 'all' || task.board_name === filters.board; - - const matchesList = - filters.list === 'all' || task.list_name === filters.list; - - const matchesAssignee = - filters.assignee === 'all' || - (filters.assignee === 'mine' && task.is_assigned_to_current_user) || - (filters.assignee === 'unassigned' && - (!task.assignees || task.assignees.length === 0)); - - return ( - matchesSearch && - matchesPriority && - matchesStatus && - matchesBoard && - matchesList && - matchesAssignee - ); + return matchesPriority && matchesStatus; }); }
180-184
: Add more robust handling for initials generationThe current implementation might produce unexpected results for certain edge cases.
export function generateAssigneeInitials(assignee: { display_name?: string; email?: string; }): string { - return ( - assignee.display_name?.[0] || - assignee.email?.[0] || - '?' - ).toUpperCase(); + const name = assignee.display_name?.trim(); + const email = assignee.email?.trim(); + + if (name) { + // Handle names with multiple parts (e.g., "John Doe" -> "JD") + const parts = name.split(/\s+/).filter(Boolean); + if (parts.length >= 2) { + return `${parts[0][0]}${parts[1][0]}`.toUpperCase(); + } + return name[0].toUpperCase(); + } + + if (email) { + // Use part before @ for email + const username = email.split('@')[0]; + return username[0].toUpperCase(); + } + + return '?'; }apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (2)
61-198
: Simplify onboarding state logicThe onboarding state management is complex with nested conditions. Consider extracting this logic into a custom hook.
// useOnboardingState.ts export function useOnboardingState(viewMode: string) { const DISMISSAL_DURATION_DAYS = 7; const NEW_USER_VIEW_THRESHOLD = 3; const EXPERIENCED_USER_REMINDER_DAYS = 14; const [state, setState] = useState<OnboardingState>(() => loadOnboardingState() ); const shouldShowTips = useMemo(() => { if (!state.showTips) return false; const isNewUser = state.viewCount < NEW_USER_VIEW_THRESHOLD; const viewModeChanged = state.lastViewMode !== viewMode; const shouldShowPeriodicReminder = checkPeriodicReminder(state); return isNewUser || viewModeChanged || shouldShowPeriodicReminder; }, [state, viewMode]); const dismissTips = useCallback(() => { const newState = { ...state, showTips: false, dismissedAt: new Date().toISOString() }; setState(newState); saveOnboardingState(newState); }, [state]); // Track view mode changes useEffect(() => { if (viewMode !== state.lastViewMode) { incrementViewCount(viewMode); } }, [viewMode, state.lastViewMode]); return { shouldShowTips, dismissTips }; }
1-1507
: Add comprehensive unit tests for this componentThe static analysis shows that most of this component is not covered by tests. Given its complexity and importance, comprehensive testing is essential.
Would you like me to help generate a comprehensive test suite for this component? The tests should cover:
- Different view modes rendering correctly
- Onboarding state management
- User interactions (settings changes, navigation)
- Edge cases (empty data, invalid dates)
- Accessibility features
This would significantly improve code confidence and maintainability.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (3)
344-344
: Improve badge display clarity and string handling.Two minor improvements:
- The focus score lacks a unit indicator (line 344)
- String replacement only handles first occurrence (line 352)
- <span>Focus {avgFocusScore}</span> + <span>Focus {avgFocusScore}%</span>- <span className="capitalize">{productivityType.replace('-', ' ')}</span> + <span className="capitalize">{productivityType.replaceAll('-', ' ')}</span>Also applies to: 352-352
1316-1318
: Consider accessibility improvements for emoji usage.The filter options use emojis that might not be accessible to screen reader users. Consider adding aria-labels or using icon components instead.
Example improvement for one of the select items:
- <SelectItem value="short">🏃 Short (< 30 min)</SelectItem> + <SelectItem value="short" aria-label="Short sessions less than 30 minutes">🏃 Short (< 30 min)</SelectItem>Also applies to: 1341-1345, 1364-1367, 1386-1390, 1409-1412
763-2205
: Consider decomposing this large component for better maintainability.The SessionHistory component is over 1400 lines and handles many responsibilities. While the implementation is solid, consider extracting some parts into separate components:
- Analytics calculation functions → separate utility module
- Filter UI → FilterPanel component
- Month view analytics cards → MonthViewAnalytics component
- AI insights section → AIInsights component
This would improve:
- Code organization and readability
- Testability of individual parts
- Performance through better memoization opportunities
- Team collaboration on different features
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx (1)
1242-1244
: MemoisegetFilteredTasks
to avoid O(N) work on every render
getFilteredTasks()
is recomputed multiple times per render (keyboard navigation, counts, etc.).
Wrap it inuseMemo
with[tasks, taskSearchQuery, taskFilters]
deps or move filtering into the shared util hook.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
(2 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx
(4 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
(9 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx
(2 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
(16 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx
(30 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
(1 hunks)apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
(3 hunks)apps/web/src/components/command/quick-actions.tsx
(4 hunks)apps/web/src/components/command/quick-time-tracker.tsx
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
ExtendedWorkspaceTask
(40-52)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (3)
ExtendedWorkspaceTask
(40-52)TaskFilters
(55-61)TaskSidebarFilters
(64-68)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
packages/types/src/db.ts (3)
TimeTrackingSession
(74-74)TimeTrackingCategory
(73-73)WorkspaceTask
(23-23)
🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
[error] 129-130: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
[error] 754-755: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 817-817: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 1083-1083: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 1087-1087: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 1095-1096: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx
[warning] 72-72: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L72
Added line #L72 was not covered by tests
[warning] 1117-1117: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1117
Added line #L1117 was not covered by tests
[warning] 1295-1295: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1295
Added line #L1295 was not covered by tests
[warning] 1962-1962: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1962
Added line #L1962 was not covered by tests
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
[warning] 13-13: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L13
Added line #L13 was not covered by tests
[warning] 189-189: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L189
Added line #L189 was not covered by tests
[warning] 193-203: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L193-L203
Added lines #L193 - L203 were not covered by tests
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
[warning] 4-14: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L4-L14
Added lines #L4 - L14 were not covered by tests
[warning] 20-22: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L20-L22
Added lines #L20 - L22 were not covered by tests
[warning] 27-28: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27-L28
Added lines #L27 - L28 were not covered by tests
[warning] 38-46: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L38-L46
Added lines #L38 - L46 were not covered by tests
[warning] 52-52: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L52
Added line #L52 was not covered by tests
[warning] 54-210: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L54-L210
Added lines #L54 - L210 were not covered by tests
[warning] 374-374: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L374
Added line #L374 was not covered by tests
[warning] 401-401: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401
Added line #L401 was not covered by tests
[warning] 403-403: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L403
Added line #L403 was not covered by tests
[warning] 417-417: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L417
Added line #L417 was not covered by tests
[warning] 434-434: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L434
Added line #L434 was not covered by tests
[warning] 436-437: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L436-L437
Added lines #L436 - L437 were not covered by tests
[warning] 439-444: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L439-L444
Added lines #L439 - L444 were not covered by tests
[warning] 446-448: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L446-L448
Added lines #L446 - L448 were not covered by tests
[warning] 459-459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L459
Added line #L459 was not covered by tests
[warning] 461-462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L461-L462
Added lines #L461 - L462 were not covered by tests
[warning] 467-471: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L467-L471
Added lines #L467 - L471 were not covered by tests
[warning] 551-1238: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L551-L1238
Added lines #L551 - L1238 were not covered by tests
[warning] 1240-1240: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1240
Added line #L1240 was not covered by tests
[warning] 1253-1348: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1253-L1348
Added lines #L1253 - L1348 were not covered by tests
[warning] 1353-1382: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1353-L1382
Added lines #L1353 - L1382 were not covered by tests
[warning] 1384-1393: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1384-L1393
Added lines #L1384 - L1393 were not covered by tests
[warning] 1395-1396: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1395-L1396
Added lines #L1395 - L1396 were not covered by tests
⏰ Context from checks skipped due to timeout of 90000ms (7)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
🔇 Additional comments (9)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
40-52
: Optional fields inExtendedWorkspaceTask
may mask data-integrity issues
Every extra property is declared optional, meaningundefined
quietly propagates. Ifboard_name
,list_name
, orassignee_name
are always expected once tasks are enriched, mark them as required or document why they can be absent. This tightens type-safety and surfaces backend/transform bugs earlier.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx (1)
7-8
: Good move centralising types
ImportingTimerStats
from the shared module removes duplication and keeps the component lean.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx (1)
3-4
: Consistent shared-type usage acknowledged
Switching toTimerStats
/TimeTrackingGoal
from the central file improves maintainability.apps/web/src/components/command/quick-actions.tsx (2)
66-72
: Icon import safety
Brain
is assumed to exist inlucide-react
; if the package version lacks it CI will fail. Verify the icon is present or use a fallback.
97-108
: Descriptive copy 👍
The analytics badge and updated description immediately convey added value to users.apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
13-13
: Good call centralising the type declarationImporting
ExtendedWorkspaceTask
from the sharedtime-tracker/types
module removes local duplication and keeps all task-related types in one place.apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (1)
72-72
: 👍 Unified type importsReplacing local interface declarations with the shared ones improves consistency across components and prevents divergence.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)
838-847
: Use optional chaining for safer property access.The current implementation could throw if
task
is undefined or null.- const task = tasks.find(t => t.id === session.task_id); - return task?.board_name || 'project-work'; + const task = tasks.find(t => t.id === session.task_id); + return task?.board_name ?? 'project-work';Likely an incorrect or invalid review comment.
275-282
: Ensure dynamic Tailwind classes are preserved in production.The dynamic class generation for productivity colors might get purged by Tailwind CSS in production builds. These classes need to be safelisted in your Tailwind configuration.
#!/bin/bash # Check if Tailwind config includes safelist for these dynamic classes fd -e js -e ts "tailwind.config" --exec grep -l "safelist" {} \; | head -5 # Also check for any existing safelist patterns fd -e js -e ts "tailwind.config" --exec grep -A 10 -B 2 "safelist" {} \; | head -30
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx
Outdated
Show resolved
Hide resolved
- Add comprehensive session protection to prevent mode switching during active timers - Implement separate break time tracking for each timer mode (stopwatch, Pomodoro, custom) - Add visual lock indicators and error messages when attempting restricted actions - Create mode-specific break state persistence with localStorage support - Enhance session restoration to maintain state when switching between timer modes - Add session protection for timer mode selector, settings access, and custom type switching - Implement mode-aware break reminders with separate counters per timer type - Prevent data corruption and integrity errors through isolated state management - Add comprehensive validation across all user interaction points - Maintain backward compatibility while enhancing data safety and user experience
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
175-178
: Avoid in-place sorting to prevent hidden side-effects
sessions.sort(...)
mutates the original array that is also referenced elsewhere.
Clone first to keep the helper pure:- const sortedSessions = sessions.sort((a, b) => + const sortedSessions = [...sessions].sort((a, b) => dayjs(a.start_time).diff(dayjs(b.start_time)) );
♻️ Duplicate comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
842-845
: Magic string check repeatedStill relying on the literal
'resumed'
insidesession.description
to detect interruptions. This fragility was highlighted in an earlier review; please move to an explicitwas_resumed
flag (or similar) on the session model.
🧹 Nitpick comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
1023-1026
: Simplify & harden best-time-of-day calculationCurrent reduce logic is hard to read and accesses
timeOfDayBreakdown
inside the callback on both operands. A clearer, O(n) approach:const bestTimeOfDay = Object.entries(timeOfDayBreakdown) .sort(([, a], [, b]) => b - a)[0]?.[0] ?? 'morning';This also guards against unexpected empty objects.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
(17 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
[error] 592-593: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- Replace 'any' types with proper TypeScript interfaces in task data transformation - Add TaskAssigneeData, TaskListData, and RawTaskData interfaces for better type safety - Improve type checking in assignee filtering and mapping logic Addresses code review feedback about type safety in API data handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)
40-44
: Incorrectparams
typing – may break type-checking and IntelliSense
params
in a Next.js route handler is a plain object, not aPromise
.
Declaring it asPromise<{ wsId: string }>
forces consumers toawait
an object, and propagates the wrong type to downstream code.-export async function GET( - request: NextRequest, - { params }: { params: Promise<{ wsId: string }> } +export async function GET( + request: NextRequest, + { params }: { params: { wsId: string } } )Apply the same fix to the
POST
handler (lines 182-186).
🧹 Nitpick comments (2)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)
24-36
:RawTaskData
misses columns used later (deleted
)The query filters with
.eq('deleted', false)
(line 113) yetdeleted
is not part ofRawTaskData
.
Add the field so future refactors or narrowing by keyed access don’t introduce unsound code.interface RawTaskData { id: string; name: string; ... list_id: string; task_lists: TaskListData | null; assignees?: TaskAssigneeData[]; + deleted?: boolean; }
158-167
: Avoid shadowing the outeruser
and simplify de-duplicationThe parameter name
user
inside the.filter
callback hides the authenticated user captured in the outer scope, which is error-prone. Renaming also clears the static-analysis optional-chain warning.- task.assignees - ?.map((a: TaskAssigneeData) => a.user) - .filter( - (user, index: number, self) => - user && - user.id && - self.findIndex((u) => u?.id === user.id) === index - ) || [], + task.assignees + ?.map((a) => a.user) + .filter( + (assigneeUser, idx, arr) => + assigneeUser?.id && + arr.findIndex((u) => u?.id === assigneeUser.id) === idx + ) ?? [],
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
(4 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
[error] 163-164: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)
112-114
: Verify Supabase path filterThe chained filter
eq('task_lists.workspace_boards.ws_id', wsId)
is three levels deep. Supabase’s PostgREST path filtering usually supports one implicit join (table!inner(column)
).
If this path is not accepted the query will silently fail and return all tasks.Please confirm in a test workspace; if unsupported you’ll need an explicit join (
.eq('task_lists.workspace_boards.ws_id', wsId)
→ join or RPC).
- Use refs to access elapsedTime and hasReachedTarget in interval callbacks to prevent recreation every second - Remove frequently changing state from useEffect dependencies in timer intervals - Update checkBreakReminders to use elapsedTimeRef instead of direct elapsedTime dependency - Reduces CPU usage from ~60 effect recreations per minute to only when settings change Fixes interval being recreated every tick which was defeating the purpose of setInterval and causing unnecessary renders.
- Add guard to check targetTime > 0 before division to prevent NaN - Set progress bar width to 0% when targetTime is 0 (enhanced-stopwatch mode) - Prevents CSS rendering issues when timer mode doesn't use countdown Fixes progress bar breaking in enhanced-stopwatch mode where targetTime can be 0.
- Use singleton AudioContext with useRef instead of creating new instance per sound - Add lazy initialization to only create AudioContext when needed - Resume suspended context automatically for browser compatibility - Add proper cleanup on component unmount to prevent memory leaks Fixes "too many AudioContexts" browser error during long timer sessions.
…ssions - Store only minimal session identifiers instead of full SessionWithRelations objects - Add fetchSessionById function to re-fetch session details from server when needed - Implement PausedSessionData interface with only non-sensitive data (sessionId, elapsed, pauseTime, timerMode) - Add automatic cleanup for invalid/deleted sessions - Maintain legacy key cleanup for smooth migration Prevents PII exposure and localStorage quota issues while ensuring data freshness.
- Add was_resumed field to time_tracking_sessions table - Update API to set field when resuming sessions - Replace description parsing with reliable boolean check - Add database index for analytics performance Fixes fragile magic string dependency in focus score calculation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql (1)
13-15
: RedundantDROP VIEW
beforeCREATE OR REPLACE
CREATE OR REPLACE VIEW
already replaces the view atomically.
The precedingDROP VIEW IF EXISTS
incurs an extra invalidation period and requires super-user rights on some systems. You can remove lines 13-14 without changing behaviour.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
589-597
: SimplifyprevSession
lookup & use optional chainingThe ternary inside
prevSession
is identical on both branches, and the subsequent null-guard can be shortened with?.
. This makes the intent clearer and removes the Biome warning.-const prevSession = actualIndex > 0 ? - (showAllSessions ? stackedSession.sessions[index - 1] : stackedSession.sessions[index - 1]) : null; -const gapInSeconds = - prevSession && prevSession.end_time - ? sessionStart.diff( - dayjs.utc(prevSession.end_time).tz(userTimezone), - 'seconds' - ) - : null; +const prevSession = + actualIndex > 0 ? stackedSession.sessions[index - 1] : null; + +const gapInSeconds = prevSession?.end_time + ? sessionStart.diff( + dayjs.utc(prevSession.end_time).tz(userTimezone), + 'seconds' + ) + : null;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
(17 hunks)apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
(2 hunks)packages/types/src/supabase.ts
(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (5)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
SessionWithRelations
(22-25)packages/ui/src/components/ui/badge.tsx (1)
Badge
(49-49)packages/ui/src/components/ui/dropdown-menu.tsx (5)
DropdownMenu
(241-241)DropdownMenuTrigger
(255-255)DropdownMenuContent
(243-243)DropdownMenuItem
(245-245)DropdownMenuSeparator
(250-250)packages/ui/src/components/ui/popover.tsx (3)
PopoverTrigger
(47-47)PopoverContent
(47-47)Popover
(47-47)packages/ui/src/components/ui/select.tsx (5)
Select
(170-170)SelectTrigger
(178-178)SelectValue
(179-179)SelectContent
(171-171)SelectItem
(173-173)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
[error] 592-593: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
🔇 Additional comments (3)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (1)
117-133
: Validate session state before creating a “resumed” clone.
resume
currently inserts a new running session even if the original session is still markedis_running = true
.
This opens the door to multiple concurrent “running” sessions for the same user, which breaks downstream analytics and any uniqueness constraints you may have (e.g. “only one active session per user/workspace”).A minimal guard fixes the issue:
-if (action === 'resume') { +if (action === 'resume') { + // Safety-check: only allow resume on a stopped / paused session + if (session.is_running) { + return NextResponse.json( + { error: 'Cannot resume a session that is still running' }, + { status: 400 } + ); + }Optionally, wrap the guard + insert in a transaction (RPC or Postgres function) to avoid race conditions in high-latency scenarios.
[ suggest_essential_refactor ]packages/types/src/supabase.ts (2)
7128-7132
: Heat-map analytics view keepswas_resumed
in sync – LGTMThe new field is propagated to
time_tracking_session_analytics.Row
, keeping the view API consistent withtime_tracking_sessions
. No further issues spotted here.
3782-3836
: To confirm whether thewas_resumed
column is declaredNOT NULL DEFAULT false
, let’s locate its definition in your SQL migrations:#!/usr/bin/env bash set -e # Search all SQL files for the was_resumed column definition rg "was_resumed" -n --glob "*.sql"
apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
Outdated
Show resolved
Hide resolved
- Implemented filtering logic for session data to enhance heatmap accuracy. - Updated heatmap component to visualize filtered session data effectively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)
666-681
: Buttons missing explicittype="button"
The calendar navigation arrows are plain<button>
elements inside a form-less component. In a future integration where the heatmap is placed inside a form, the defaulttype="submit"
will accidentally submit the form. Addtype="button"
explicitly:-<Button variant="ghost" size="icon" ...> +<Button variant="ghost" size="icon" type="button" ...>(Same for the right-arrow.)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
589-598
: Unnecessary complexity & missing optional-chain in gap calculation
const actualIndex = showAllSessions ? index : index
is always equal toindex
.prevSession && prevSession.end_time
can be simplified with optional chaining, silencing the Biome hint and improving readability.-const actualIndex = showAllSessions ? index : index; -const prevSession = actualIndex > 0 ? - (showAllSessions ? stackedSession.sessions[index - 1] : stackedSession.sessions[index - 1]) : null; -const gapInSeconds = - prevSession && prevSession.end_time +const prevSession = index > 0 ? stackedSession.sessions[index - 1] : null; +const gapInSeconds = + prevSession?.end_time ? sessionStart.diff( dayjs.utc(prevSession.end_time).tz(userTimezone), 'seconds' ) : null;This keeps the logic identical while trimming three lines and removes a linter warning.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
(9 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
(17 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
[warning] 4-14: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L4-L14
Added lines #L4 - L14 were not covered by tests
[warning] 20-22: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L20-L22
Added lines #L20 - L22 were not covered by tests
[warning] 27-28: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27-L28
Added lines #L27 - L28 were not covered by tests
[warning] 38-46: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L38-L46
Added lines #L38 - L46 were not covered by tests
[warning] 52-52: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L52
Added line #L52 was not covered by tests
[warning] 54-210: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L54-L210
Added lines #L54 - L210 were not covered by tests
[warning] 374-374: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L374
Added line #L374 was not covered by tests
[warning] 401-401: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401
Added line #L401 was not covered by tests
[warning] 403-403: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L403
Added line #L403 was not covered by tests
[warning] 417-417: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L417
Added line #L417 was not covered by tests
[warning] 434-434: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L434
Added line #L434 was not covered by tests
[warning] 436-437: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L436-L437
Added lines #L436 - L437 were not covered by tests
[warning] 439-444: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L439-L444
Added lines #L439 - L444 were not covered by tests
[warning] 446-448: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L446-L448
Added lines #L446 - L448 were not covered by tests
[warning] 459-459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L459
Added line #L459 was not covered by tests
[warning] 461-462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L461-L462
Added lines #L461 - L462 were not covered by tests
[warning] 467-471: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L467-L471
Added lines #L467 - L471 were not covered by tests
[warning] 551-1306: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L551-L1306
Added lines #L551 - L1306 were not covered by tests
[warning] 1308-1308: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1308
Added line #L1308 was not covered by tests
[warning] 1321-1416: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1321-L1416
Added lines #L1321 - L1416 were not covered by tests
[warning] 1421-1450: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1421-L1450
Added lines #L1421 - L1450 were not covered by tests
[warning] 1452-1461: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1452-L1461
Added lines #L1452 - L1461 were not covered by tests
[warning] 1463-1464: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1463-L1464
Added lines #L1463 - L1464 were not covered by tests
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
[error] 592-593: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)
808-829
: Streak calculation uses only active days – always produces full streaks
monthData.dates
only contains days that already have activity, so thefor
-loop never encounters a “gap”, makinglongestStreak
equal toactiveDays
. Either push all days (including inactive) intodates
or iterate over the calendar range to obtain meaningful streaks.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
Outdated
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
Outdated
Show resolved
Hide resolved
- added optional chaining, constants extraction, and data preservation - **heatmap**: Replace explicit null checks with optional chaining operators - Simplify property access patterns in activity filtering logic - Enhance code readability and maintain runtime safety - Fix static analysis warnings for complexity/useOptionalChain - **quick-timer**: Extract focus score magic numbers and simplify calculation - Add FOCUS_SCORE_CONSTANTS and SESSION_THRESHOLDS constants - Extract calculateFocusScore helper function for better maintainability - Replace complex inline IIFE with clean function call in JSX - Update all hardcoded time comparisons to use named constants - **tasks-sidebar**: preserve assignee metadata when transforming tasks - Prevent overwriting existing assignee information with undefined values - Keep assignee_name, assignee_avatar, is_assigned_to_current_user, and assignees fields - Transform tasks to ExtendedWorkspaceTask while preserving existing metadata - Fix potential data loss when tasks already contain enriched assignee information These changes improve maintainability, readability, and data integrity across the time tracking and task management components.
- added with type safety, performance, and maintainability enhancements - **types**: Add JSDoc comments to clarify time units in TimerStats interface - Document duration fields as seconds to prevent unit conversion bugs - Add time unit documentation to dailyActivity duration field - **types**: Replace empty string defaults with null for better type safety - Update TaskFilters and TaskSidebarFilters to use string | null - Improve filter comparisons by avoiding truthy/falsy traps - **time-tracker**: Simplify data mapping with spread operator - Replace manual property mapping with cleaner spread syntax - Reduce code duplication in TimeTrackerData initialization - **quick-actions**: Add memoization to peak hour calculation - Extract peak hours configuration as constants - Use useMemo to prevent unnecessary recalculation on each render - Improve testability by separating config from logic - **api**: Remove redundant null check in tasks route - Supabase returns empty array, not null, when no rows match - Simplify control flow by removing unreachable branch - **utils**: Enhance task filtering and sorting utilities - Add proper null safety for priority values using Number() conversion - Consolidate filter logic to reduce duplication between timer and sidebar - Extract common filter interface and shared filtering function - Improve initials generation for multi-word names and edge cases - **timer-controls**: Add memoization to expensive task operations - Memoize filtered tasks calculation to avoid O(N) work on every render - Memoize unique boards and lists extraction for better performance - Replace function calls with memoized values in task navigation - **heatmap**: Replace explicit null checks with optional chaining operators - Simplify property access patterns in activity filtering logic - Enhance code readability and maintain runtime safety - Fix static analysis warnings for complexity/useOptionalChain - **quick-timer**: Extract focus score magic numbers and simplify calculation - Add FOCUS_SCORE_CONSTANTS and SESSION_THRESHOLDS constants - Extract calculateFocusScore helper function for better maintainability - Replace complex inline calculation with clean function call - Update all hardcoded time comparisons to use named constants Performance improvements: Memoized calculations reduce O(N) operations on every render Type safety: Added null safety guards and proper type documentation Code quality: Extracted constants, consolidated utilities, improved readability Static analysis: Fixed optional chaining warnings and removed unreachable code - Replace spread operator with explicit type-safe field mapping - Convert undefined values to null for database field compatibility - Add non-null assertions for required fields (id, name, list_id) - Preserve existing assignee metadata from partial task objects - Eliminate TypeScript errors in tasks sidebar content component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)
71-76
: Guard againstNaN
when parsinglimit
/offset
parseInt()
returnsNaN
for non-numeric values, which then propagates intoMath.min
and subsequently into.range(...)
, yielding an invalid query range and a runtime 400 error from PostgREST.-const limit = Math.min( - parseInt(url.searchParams.get('limit') || '100'), - 200 -); -const offset = parseInt(url.searchParams.get('offset') || '0'); +const parseNumber = (v: string | null, fallback: number) => { + const n = Number(v); + return Number.isFinite(n) && n >= 0 ? n : fallback; +}; + +const limit = Math.min(parseNumber(url.searchParams.get('limit'), 100), 200); +const offset = parseNumber(url.searchParams.get('offset'), 0);
♻️ Duplicate comments (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)
562-570
: Division-by-zero still yieldsInfinity
, inflating intensity for empty months
Same issue flagged earlier: whenmonthActivity.length === 0
, the expression evaluates toInfinity
andgetIntensity
returns the maximum level.- intensity: getIntensity(totalDuration / monthActivity.length || 0) + intensity: getIntensity( + monthActivity.length > 0 + ? totalDuration / monthActivity.length + : 0 + )
749-807
:processMonthlyData
recomputes every render – useuseMemo
instead ofuseCallback
You memoise the function but not its (expensive) result. Sinceweeks
is a new array each render, the callback identity changes, defeating the purpose. Switch touseMemo
:-const processMonthlyData = useCallback(() => { ... }, [weeks]); +const monthlyData = useMemo(() => { ... }, [weeks]);Then pass
monthlyData
to downstream consumers.
1272-1296
:useState
inside a plain function violates the Rules-of-Hooks
renderCompactCards()
is invoked conditionally (&& renderCompactCards()
); hooks inside it will throw an “Invalid hook call” at runtime. Extract to a capitalised component (e.g.<CompactCardsView />
) or move the state up.This was pointed out in the previous review but is still present.
🧹 Nitpick comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)
154-161
: Shadowing the outeruser
variable hides bugs and hurts readability
Inside.filter(...)
the parameter is also nameduser
, masking the authenticateduser
from the outer scope. It’s harmless today but a ticking bomb for future edits.- ?.map((a: TaskAssigneeData) => a.user) - .filter( - (user, index: number, self) => - user && - user.id && - self.findIndex((u) => u?.id === user.id) === index - ) || [], + ?.map((a: TaskAssigneeData) => a.user) + .filter( + (assignee, index, self) => + assignee?.id && + self.findIndex((u) => u?.id === assignee.id) === index + ) || [],
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
(2 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
(9 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx
(2 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
(1 hunks)apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
(4 hunks)apps/web/src/components/command/quick-actions.tsx
(4 hunks)apps/web/src/components/command/quick-time-tracker.tsx
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx
- apps/web/src/components/command/quick-actions.tsx
- apps/web/src/components/command/quick-time-tracker.tsx
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)
packages/ui/src/components/ui/tooltip.tsx (3)
Tooltip
(60-60)TooltipTrigger
(60-60)TooltipContent
(60-60)packages/ui/src/components/ui/dropdown-menu.tsx (7)
DropdownMenu
(241-241)DropdownMenuTrigger
(255-255)DropdownMenuContent
(243-243)DropdownMenuLabel
(246-246)DropdownMenuItem
(245-245)DropdownMenuSeparator
(250-250)DropdownMenuCheckboxItem
(242-242)packages/ui/src/components/ui/calendar.tsx (1)
Calendar
(211-211)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
ExtendedWorkspaceTask
(44-56)
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
[warning] 4-14: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L4-L14
Added lines #L4 - L14 were not covered by tests
[warning] 20-22: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L20-L22
Added lines #L20 - L22 were not covered by tests
[warning] 27-28: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27-L28
Added lines #L27 - L28 were not covered by tests
[warning] 38-46: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L38-L46
Added lines #L38 - L46 were not covered by tests
[warning] 52-52: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L52
Added line #L52 was not covered by tests
[warning] 54-210: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L54-L210
Added lines #L54 - L210 were not covered by tests
[warning] 374-374: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L374
Added line #L374 was not covered by tests
[warning] 401-401: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401
Added line #L401 was not covered by tests
[warning] 403-403: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L403
Added line #L403 was not covered by tests
[warning] 417-417: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L417
Added line #L417 was not covered by tests
[warning] 434-434: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L434
Added line #L434 was not covered by tests
[warning] 436-437: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L436-L437
Added lines #L436 - L437 were not covered by tests
[warning] 439-444: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L439-L444
Added lines #L439 - L444 were not covered by tests
[warning] 446-448: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L446-L448
Added lines #L446 - L448 were not covered by tests
[warning] 459-459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L459
Added line #L459 was not covered by tests
[warning] 461-462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L461-L462
Added lines #L461 - L462 were not covered by tests
[warning] 467-471: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L467-L471
Added lines #L467 - L471 were not covered by tests
[warning] 551-1306: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L551-L1306
Added lines #L551 - L1306 were not covered by tests
[warning] 1308-1308: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1308
Added line #L1308 was not covered by tests
[warning] 1321-1416: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1321-L1416
Added lines #L1321 - L1416 were not covered by tests
[warning] 1421-1450: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1421-L1450
Added lines #L1421 - L1450 were not covered by tests
[warning] 1452-1461: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1452-L1461
Added lines #L1452 - L1461 were not covered by tests
[warning] 1463-1464: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1463-L1464
Added lines #L1463 - L1464 were not covered by tests
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
[warning] 13-13: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L13
Added line #L13 was not covered by tests
[warning] 189-189: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L189
Added line #L189 was not covered by tests
[warning] 193-231: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L193-L231
Added lines #L193 - L231 were not covered by tests
🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
[error] 158-159: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (2)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)
38-44
:params
should not be typed as aPromise
Next.js
passesparams
synchronously. Typing it asPromise<{ wsId: string }>
forces an unnecessaryawait
, complicates the signature, and mis-documents the handler.-export async function GET( - request: NextRequest, - { params }: { params: Promise<{ wsId: string }> } -) { - const { wsId } = await params; +export async function GET( + request: NextRequest, + { params }: { params: { wsId: string } } +) { + const { wsId } = params;apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
1320-1359
: CSV export is vulnerable to Excel CSV-injectionIf a title or description starts with
=
,+
,-
, or@
, opening the exported CSV in Excel will execute the text as a formula.
Prefix user-controlled cells with an apostrophe to neutralise them:- return [ - startTime.format('YYYY-MM-DD'), - session.title, - … - session.description || '', - ]; + const escape = (v: string) => + /^[=\+\-@]/.test(v) ? `'${v}` : v; + + return [ + startTime.format('YYYY-MM-DD'), + escape(session.title), + … + escape(session.description || ''), + ];Small change, big security win.
♻️ Duplicate comments (4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
212-215
: Boolean fields coerced tonull
again – breaks declared typing
Previous review already covered this:archived
,completed
, anddeleted
areboolean | undefined
inWorkspaceTask
; forcing them tonull
violates the contract and propagates extra null-checks downstream.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (2)
1434-1472
: Hooks used inside an ordinary function – violates the Rules of Hooks
renderCompactCards
is invoked like a helper (renderCompactCards()
) yet declaresuseState
inside it.
Because the function is called conditionally and not as a React component (), React will throwInvalid hook call
in production.Suggested fix – promote it to a proper component:
-const renderCompactCards = () => { +function RenderCompactCards() { const monthlyData = processMonthlyData(); ... return ( <CompactCardsContainer ... /> ); }and render it
-{settings.viewMode === 'compact-cards' && renderCompactCards()} +{settings.viewMode === 'compact-cards' && <RenderCompactCards />}Rename to start with a capital letter to satisfy React’s component detection.
604-609
: Division-by-zero still yieldsInfinity
→ always highest intensity
totalDuration / monthActivity.length || 0
performs the division first, so whenmonthActivity.length
is 0 the value isInfinity
, which is truthy – the|| 0
fallback never fires.
getIntensity(Infinity)
always returns 4, painting completely inactive months as “high activity”.- intensity: getIntensity(totalDuration / monthActivity.length || 0), + intensity: getIntensity( + monthActivity.length > 0 + ? totalDuration / monthActivity.length + : 0 + ),This mirrors the earlier feedback that was marked “addressed” but is still present.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
884-889
:was_resumed
still not guaranteed on the type – compiler will complain
SessionWithRelations
is still accessed withsession.was_resumed
(line 885) but the field is not part of the original Prisma-generated model. Unless you have extended the type in../types.ts
, this will raise a TS error and the property will beundefined
at runtime, giving every session the full 20-point consistency bonus.-const consistencyBonus = session.was_resumed ? 0 : 20; +// Guard against undefined until the API/type is updated +const consistencyBonus = session.was_resumed === true ? 0 : 20;If the backend now supplies the boolean, extend the type accordingly; otherwise keep the strict comparison.
🧹 Nitpick comments (5)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)
155-161
: Simplify null-checks with optional chaining & tighten uniqueness filterThe current
filter
relies on a triple check (user && user.id && …
). Using optional chaining is terser, and avoids the lint warning surfaced by Biome.- ?.map((a: TaskAssigneeData) => a.user) - .filter( - (user, index: number, self) => - user && - user.id && - self.findIndex((u) => u?.id === user.id) === index - ) || [], + ?.map((a) => a.user) + .filter( + (user, idx, self) => + !!user?.id && self.findIndex((u) => u?.id === user.id) === idx + ) ?? [],
112-114
: Redundant post-filtering — DB query already scopes by workspace
eq('task_lists.workspace_boards.ws_id', wsId)
makes the subsequent.filter()
on line 135 redundant. Dropping the JS-side filter saves CPU and bandwidth.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)
798-861
:processMonthlyData
recomputed every render –useMemo
is a better fitThe heavy aggregation runs on every render because
useCallback
caches the function, not its result, and you call it immediately afterwards. Replace withuseMemo
(or compute once outside the component if data is static) to avoid unnecessary work:-const processMonthlyData = useCallback(() => { - ... -}, [weeks]); +const monthlyData = useMemo(() => { + ... +}, [weeks]);Downstream calls (
calculateMonthlyStats(monthlyData)
) then accept the pre-computed object.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)
617-623
: RedundantactualIndex
logic
actualIndex
is always equal toindex
– the ternary branches are identical.
This variable can be removed and the two subsequent references can just useindex
, which marginally simplifies the loop.
625-631
: Prefer optional chaining over manual null checks
prevSession && prevSession.end_time
can be simplified with an optional chain, matching the code-base style and satisfying the Biome lint hint.-const gapInSeconds = - prevSession && prevSession.end_time - ? sessionStart.diff( - dayjs.utc(prevSession.end_time).tz(userTimezone), - 'seconds' - ) - : null; +const gapInSeconds = prevSession?.end_time + ? sessionStart.diff( + dayjs.utc(prevSession.end_time).tz(userTimezone), + 'seconds' + ) + : null;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
(2 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx
(4 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
(9 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
(18 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
(1 hunks)apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
(4 hunks)apps/web/src/components/command/quick-actions.tsx
(4 hunks)apps/web/src/components/command/quick-time-tracker.tsx
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx
- apps/web/src/components/command/quick-time-tracker.tsx
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
- apps/web/src/components/command/quick-actions.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
ExtendedWorkspaceTask
(48-60)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
[error] 625-626: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
[error] 158-159: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
[warning] 3-21: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L3-L21
Added lines #L3 - L21 were not covered by tests
[warning] 25-25: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L25
Added line #L25 was not covered by tests
[warning] 27-27: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27
Added line #L27 was not covered by tests
[warning] 30-30: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L30
Added line #L30 was not covered by tests
[warning] 35-36: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L35-L36
Added lines #L35 - L36 were not covered by tests
[warning] 46-58: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L46-L58
Added lines #L46 - L58 were not covered by tests
[warning] 64-64: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L64
Added line #L64 was not covered by tests
[warning] 66-237: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L66-L237
Added lines #L66 - L237 were not covered by tests
[warning] 401-406: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401-L406
Added lines #L401 - L406 were not covered by tests
[warning] 433-433: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L433
Added line #L433 was not covered by tests
[warning] 435-435: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L435
Added line #L435 was not covered by tests
[warning] 449-449: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L449
Added line #L449 was not covered by tests
[warning] 466-466: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L466
Added line #L466 was not covered by tests
[warning] 468-469: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L468-L469
Added lines #L468 - L469 were not covered by tests
[warning] 471-476: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L471-L476
Added lines #L471 - L476 were not covered by tests
[warning] 478-480: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L478-L480
Added lines #L478 - L480 were not covered by tests
[warning] 491-491: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L491
Added line #L491 was not covered by tests
[warning] 493-494: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L493-L494
Added lines #L493 - L494 were not covered by tests
[warning] 499-503: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L499-L503
Added lines #L499 - L503 were not covered by tests
[warning] 583-1473: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L583-L1473
Added lines #L583 - L1473 were not covered by tests
[warning] 1475-1475: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1475
Added line #L1475 was not covered by tests
[warning] 1488-1520: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1488-L1520
Added lines #L1488 - L1520 were not covered by tests
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
[warning] 5-5: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L5
Added line #L5 was not covered by tests
[warning] 10-10: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L10
Added line #L10 was not covered by tests
[warning] 186-186: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L186
Added line #L186 was not covered by tests
[warning] 190-231: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L190-L231
Added lines #L190 - L231 were not covered by tests
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx
[warning] 4-8: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L4-L8
Added lines #L4 - L8 were not covered by tests
[warning] 1118-1118: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1118
Added line #L1118 was not covered by tests
[warning] 1296-1296: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1296
Added line #L1296 was not covered by tests
[warning] 1963-1963: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1963
Added line #L1963 was not covered by tests
⏰ Context from checks skipped due to timeout of 90000ms (7)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
🔇 Additional comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (2)
1118-1125
: Styling tweak looks goodThe updated button classes unify light & dark themes without affecting behaviour.
1963-1966
: Consistent button styling – LGTMSame comment as above; keeps UX consistent across dialogs.
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
Outdated
Show resolved
Hide resolved
2. fix(types): resolve TaskSidebarFilters type compatibility issues 3. fix(time-tracker): remove unused variables and improve string access safety
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (2)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)
70-79
: NaN/negativelimit
&offset
→ runtime DB error
parseInt()
returnsNaN
for non-numeric input, which then propagates intoMath.min
/range()
and makes Supabase reject the query.
A malicious / accidental?limit=foo
will therefore 500 the route.-const limit = Math.min( - parseInt(url.searchParams.get('limit') || '100'), - 200 -); -const offset = parseInt(url.searchParams.get('offset') || '0'); +const parsedLimit = Number.parseInt(url.searchParams.get('limit') ?? '', 10); +const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 200) : 100; + +const parsedOffset = Number.parseInt(url.searchParams.get('offset') ?? '', 10); +const offset = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;This keeps the endpoint resilient against bad input.
79-113
:eq('task_lists.workspace_boards.ws_id', …)
is not valid for the JS query-builderDeep-path filters of the form
table1.table2.column
only work in the REST endpoint syntax.
In the JS client, you need to:
- use an
!inner
join onworkspace_boards
, and- filter directly on that alias, e.g.:
- .eq('task_lists.workspace_boards.ws_id', wsId) + .eq('workspace_boards.ws_id', wsId) // or push the filter into the inner joinAs written, Postgres will complain that the column does not exist and the handler will 500.
♻️ Duplicate comments (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)
1434-1449
: Hooks used inside a plain function – violates the Rules-of-Hooks
renderCompactCards()
is invoked as a regular function ({settings.viewMode === 'compact-cards' && renderCompactCards()}
) but it contains auseState
call (line 1438).
React hooks must run only inside React components (capitalised function invoked as JSX) or inside custom hooks, never in an arbitrary helper that may be called conditionally. This will trigger the “Invalid hook call” runtime error as soon as the compact-cards mode is rendered.-const renderCompactCards = () => { +function CompactCardsView() { const monthlyData = processMonthlyData(); ... const [currentIndex, setCurrentIndex] = useState(0); ... - return (<CompactCardsContainer ... />); + return <CompactCardsContainer ... />; } ... -{settings.viewMode === 'compact-cards' && renderCompactCards()} +{settings.viewMode === 'compact-cards' && <CompactCardsView />}Rename the helper to start with a capital letter and render it via JSX to satisfy the Rules-of-Hooks.
604-609
: Division-by-zero still yieldsInfinity
, causing false “high-activity” months
getIntensity(totalDuration / monthActivity.length || 0)
tries to fall back to0
but the||
short-circuit never fires becauseInfinity
is truthy. An emptymonthActivity
therefore gets intensity4
, painting completely idle months as dark green.- intensity: getIntensity(totalDuration / monthActivity.length || 0), + intensity: getIntensity( + monthActivity.length > 0 + ? totalDuration / monthActivity.length + : 0, + ),This mirrors the earlier bot comment that was marked “addressed” but is still present.
798-861
:processMonthlyData
memoisation ineffective –useCallback
around a function, not the result
weeks
is recreated every render, so theuseCallback
dependency changes each time, producing a fresh function and defeating memoisation. More importantly, the heavy computation is re-executed again viaprocessMonthlyData()
.Prefer caching the computed data with
useMemo
:-const processMonthlyData = useCallback(() => { - return weeks.reduce( ... ) -}, [weeks]); +const monthlyData = useMemo(() => { + return weeks.reduce( ... ); +}, [weeks]);Downstream callers (
calculateMonthlyStats
, card builders, etc.) can then consumemonthlyData
directly.
🧹 Nitpick comments (4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (2)
895-909
: Trend calculation may yield NaN / Infinity when previous month is emptyWhen
prevAvg === 0
the%
computation divides by zero:trendValue = ((currentAvg - prevAvg) / Math.max(prevAvg, 1)) * 100;
Math.max(prevAvg, 1)
avoids Infinity, buttrend
above is still decided by comparing againstprevAvg * 1.1
/0.9
, both zero, so any non-zerocurrentAvg
is considered “up” by > 90 %. Consider treating a zero-activity previous month as neutral.if (previousMonth && prevAvg > 0) { ... } else { trend = 'neutral'; trendValue = 0; }Prevents misleading “↗ ∞%” spikes.
1554-1563
: Checkbox toggles only between ‘smart’ and ‘relative’, silently losing ‘absolute’The “Show smart time references” checkbox sets
timeReference
to'smart'
or'relative'
, but the settings type supports'absolute'
as well. If a user previously selected'absolute'
, this toggle will overwrite it, making it impossible to return without a full settings reset.Consider replacing the single checkbox with a radio-group or preserving the previous non-smart choice.
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)
4-36
: Centralise DTO types and keep them in sync with the SELECT listThe three interfaces are very useful, but duplicating them inside every route quickly drifts from reality.
We already haveExtendedWorkspaceTask
in a sharedtypes.ts
; these “raw DB” DTOs should live next to it (or be generated with Zod) and be imported here, rather than re-declared.While moving them, please also add the two fields that are currently missing from the type surface you use later:
board_id
onTaskListData
(line 96 of theSELECT
).deleted
onRawTaskData
(line 113 filter).That way TypeScript will catch any divergence the next time the schema evolves.
127-131
: Avoid re-logging & re-throwing raw DB errors
console.error
+throw
here is immediately followed by anothercatch
that does the same.
Consider wrapping the error (throw new Error('TASKS_QUERY_FAILED')
) or returning a typed Supabase error to prevent duplicate stack traces and accidental leakage of DB internals in production logs.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
(9 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
(1 hunks)apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
🧰 Additional context used
🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
[error] 158-159: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (10)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Run tests and collect coverage
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)
175-178
: Avoid mutating the originalsessions
array when sorting
Array.prototype.sort
mutates in-place. BecausegroupSessions
is a reference stored ingroups
, any later consumer of that array will now receive the sorted version, which may produce subtle side-effects if order was expected to be stable. Clone before sorting:-const sortedSessions = groupSessions.sort((a, b) => - dayjs(a.start_time).diff(dayjs(b.start_time)) -); +const sortedSessions = [...groupSessions].sort((a, b) => + dayjs(a.start_time).diff(dayjs(b.start_time)) +);
1325-1346
: Rename helper and fully escape CSV values to avoid clobbering the globalescape
and malformed CSV output
- Shadowing the global
escape
function triggers the Biome warning and is confusing.- Wrapping every field in double quotes without doubling inner
"
produces invalid CSV when values themselves contain quotes.- The current helper only prefixes
'
for formula-injection, but ignores commas / newlines.-const escape = (v: string) => - /^[=+\-@]/.test(v) ? `'${v}` : v; +const toSafeCsv = (v: string) => { + // Excel/Sheets formula injection guard + const safe = /^[=+\-@]/.test(v) ? `'${v}` : v; + // Escape double quotes by doubling them + return safe.replace(/"/g, '""'); +};and later:
-escape(session.title), -escape(session.category?.name || ''), -escape(session.task?.name || ''), +toSafeCsv(session.title), +toSafeCsv(session.category?.name || ''), +toSafeCsv(session.task?.name || ''), … -escape(session.description || ''), +toSafeCsv(session.description || ''),
♻️ Duplicate comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
218-222
: Booleanundefined
➜null
coercion re-introduced
This exact issue was pointed out in an earlier review:archived
,completed
, anddeleted
are typed asboolean | undefined
. Converting them tonull
violates the type contract and forces extra null checks everywhere else.Please keep the original value (
true/false/undefined
) or provide an explicit boolean.
🧹 Nitpick comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)
801-865
: In-place sort mutates memoised state
calculateMonthlyStats
sortsmonthData.dates
in-place. BecausemonthlyData
is memoised, subsequent renders receive an already-sorted array, but thesort
runs again and still mutates it. Not harmful today, yet it violates the expectation that memoised data is immutable.Consider copying before sorting:
const sortedDates = [...monthData.dates].sort( (a, b) => a.date.valueOf() - b.date.valueOf() );Keeps the helper pure and future-proof.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
929-941
:getProjectContext
performs an O(n) search for every session – cache or pre-indexFor large
sessions
×tasks
sets this becomes quadratic. Build aMap<taskId, Task>
once (e.g. inside auseMemo
) and do O(1) look-ups inside the filter pipeline.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql
(1 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
(2 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
(9 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
(20 hunks)apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
- apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
ExtendedWorkspaceTask
(48-60)packages/types/src/db.ts (1)
WorkspaceTask
(23-23)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
[error] 1325-1326: Do not shadow the global "escape" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
[warning] 3-21: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L3-L21
Added lines #L3 - L21 were not covered by tests
[warning] 25-25: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L25
Added line #L25 was not covered by tests
[warning] 27-27: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27
Added line #L27 was not covered by tests
[warning] 30-30: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L30
Added line #L30 was not covered by tests
[warning] 35-36: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L35-L36
Added lines #L35 - L36 were not covered by tests
[warning] 46-58: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L46-L58
Added lines #L46 - L58 were not covered by tests
[warning] 64-64: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L64
Added line #L64 was not covered by tests
[warning] 66-237: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L66-L237
Added lines #L66 - L237 were not covered by tests
[warning] 401-406: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401-L406
Added lines #L401 - L406 were not covered by tests
[warning] 433-433: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L433
Added line #L433 was not covered by tests
[warning] 435-435: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L435
Added line #L435 was not covered by tests
[warning] 449-449: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L449
Added line #L449 was not covered by tests
[warning] 466-466: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L466
Added line #L466 was not covered by tests
[warning] 468-469: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L468-L469
Added lines #L468 - L469 were not covered by tests
[warning] 471-476: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L471-L476
Added lines #L471 - L476 were not covered by tests
[warning] 478-480: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L478-L480
Added lines #L478 - L480 were not covered by tests
[warning] 491-491: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L491
Added line #L491 was not covered by tests
[warning] 493-494: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L493-L494
Added lines #L493 - L494 were not covered by tests
[warning] 499-503: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L499-L503
Added lines #L499 - L503 were not covered by tests
[warning] 583-1460: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L583-L1460
Added lines #L583 - L1460 were not covered by tests
[warning] 1462-1462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1462
Added line #L1462 was not covered by tests
[warning] 1475-1507: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1475-L1507
Added lines #L1475 - L1507 were not covered by tests
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
[warning] 5-5: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L5
Added line #L5 was not covered by tests
[warning] 10-10: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L10
Added line #L10 was not covered by tests
[warning] 186-186: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L186
Added line #L186 was not covered by tests
[warning] 190-238: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L190-L238
Added lines #L190 - L238 were not covered by tests
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Prettier Check (23)
- GitHub Check: Deploy-Preview
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
598-607
: Index mismatch whenshowAllSessions
is false could mis-compute gaps
prevSession
is taken fromstackedSession.sessions[index - 1]
, butindex
is the position insidevisibleSessions
. When only the firstINITIAL_SESSION_LIMIT
items are shown,index
still matches – but once you toggle to “show more” and then collapse again, the mapping can be off and yield negative/incorrect gaps. Consider derivingprevSession
fromvisibleSessions
instead:const prevSession = index > 0 ? visibleSessions[index - 1] : null;
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
Show resolved
Hide resolved
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)
1327-1341
: Rename helper to avoid shadowing globalescape
.The locally-defined
escape
function collides with the (deprecated but still present) globalwindow.escape
, which static-analysis already warns about. While not fatal, shadowing well-known globals is confusing and can lead to subtle bugs if the global is ever referenced in the same scope (e.g. in browser dev-tools).-const escape = (v: string) => (/^[=+\-@]/.test(v) ? `'${v}` : v); +const csvEscape = (v: string) => (/^[=+\-@]/.test(v) ? `'${v}` : v); … - return [ - startTime.format('YYYY-MM-DD'), - escape(session.title), - escape(session.category?.name || ''), - escape(session.task?.name || ''), + return [ + startTime.format('YYYY-MM-DD'), + csvEscape(session.title), + csvEscape(session.category?.name || ''), + csvEscape(session.task?.name || ''), … - escape(session.description || ''), + csvEscape(session.description || ''), ];
175-177
: Avoid mutating the originalsessions
array when sorting.
Array.prototype.sort
mutates its target in-place.
BecausecreateStackedSession
receives the same array reference held elsewhere (e.g.groups[groupKey]
), this silently re-orders the source collection and can cause hard-to-trace side-effects if any caller still relies on the original order.-const sortedSessions = sessions.sort((a, b) => +const sortedSessions = [...sessions].sort((a, b) => dayjs(a.start_time).diff(dayjs(b.start_time)) );
♻️ Duplicate comments (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2)
222-226
: Stop coercing booleans tonull
The same issue raised earlier is still present: convertingundefined
→null
forarchived
,completed
,deleted
breaks the declared unionboolean | undefined
. Keep them as-is (task.archived
, etc.) so consumers don’t have to handle a third state.
208-237
: Object reconstruction still drops fields & violates boolean typings
- Re-creating the object field-by-field means every property you forget today (e.g.
status
,labels
, custom columns) silently disappears downstream – the exact problem flagged in the previous review.archived | completed | deleted
areboolean | undefined
inWorkspaceTask
; coercing them tonull
breaks the type contract and forces extranull
checks elsewhere.Prefer spreading the original task first, then override / extend only what’s really new:
-const extendedTask: ExtendedWorkspaceTask = { - // Required fields - id: task.id!, - name: task.name!, - ... - archived: task.archived ?? null, - completed: task.completed ?? null, - deleted: task.deleted ?? null, +const extendedTask: ExtendedWorkspaceTask = { + ...task, // retain *all* existing properties & correct types + board_name: board.name, + list_name: list.name, + // Preserve assignee metadata + assignee_name: taskMeta.assignee_name, + assignee_avatar: taskMeta.assignee_avatar, + is_assigned_to_current_user: taskMeta.is_assigned_to_current_user, + assignees: taskMeta.assignees, };This also removes the need for the
!
non-null assertions on core fields.apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)
1101-1132
: Mini-heatmap still hard-codes 4 weeks → truncates long months
RenderingArray.from({ length: 7 * 4 })
omits days 29-31 for most months – the exact problem highlighted in the previous review.- {Array.from({ length: 7 * 4 }, (_, i) => { + {Array.from({ length: 7 * 6 }, (_, i) => {…and adjust the iteration to stop when
currentDay.month() !== monthStart.month()
so you don’t leak next-month cells.
🧹 Nitpick comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
195-201
: Interface declaration inside loop – hoist to avoid redundant allocations
TaskWithAssigneeMeta
is re-created on every iteration oflist.tasks.map
.
Hoist the interface (or a type alias) to file scope so the compiler emits it only once and runtime memory isn’t wasted.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
(2 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
(9 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
(20 hunks)apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
(1 hunks)apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
- apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)
packages/ui/src/components/ui/tooltip.tsx (3)
Tooltip
(60-60)TooltipTrigger
(60-60)TooltipContent
(60-60)packages/ui/src/components/ui/dropdown-menu.tsx (7)
DropdownMenu
(241-241)DropdownMenuTrigger
(255-255)DropdownMenuContent
(243-243)DropdownMenuLabel
(246-246)DropdownMenuItem
(245-245)DropdownMenuSeparator
(250-250)DropdownMenuCheckboxItem
(242-242)packages/ui/src/components/ui/calendar.tsx (1)
Calendar
(211-211)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
ExtendedWorkspaceTask
(48-60)packages/types/src/db.ts (1)
WorkspaceTask
(23-23)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx
[error] 1327-1327: Do not shadow the global "escape" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx
[warning] 3-21: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L3-L21
Added lines #L3 - L21 were not covered by tests
[warning] 25-25: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L25
Added line #L25 was not covered by tests
[warning] 27-27: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27
Added line #L27 was not covered by tests
[warning] 30-30: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L30
Added line #L30 was not covered by tests
[warning] 35-36: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L35-L36
Added lines #L35 - L36 were not covered by tests
[warning] 46-58: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L46-L58
Added lines #L46 - L58 were not covered by tests
[warning] 64-64: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L64
Added line #L64 was not covered by tests
[warning] 66-237: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L66-L237
Added lines #L66 - L237 were not covered by tests
[warning] 401-406: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401-L406
Added lines #L401 - L406 were not covered by tests
[warning] 433-433: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L433
Added line #L433 was not covered by tests
[warning] 435-435: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L435
Added line #L435 was not covered by tests
[warning] 449-449: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L449
Added line #L449 was not covered by tests
[warning] 466-466: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L466
Added line #L466 was not covered by tests
[warning] 468-469: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L468-L469
Added lines #L468 - L469 were not covered by tests
[warning] 471-476: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L471-L476
Added lines #L471 - L476 were not covered by tests
[warning] 478-480: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L478-L480
Added lines #L478 - L480 were not covered by tests
[warning] 491-491: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L491
Added line #L491 was not covered by tests
[warning] 493-494: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L493-L494
Added lines #L493 - L494 were not covered by tests
[warning] 499-503: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L499-L503
Added lines #L499 - L503 were not covered by tests
[warning] 583-1457: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L583-L1457
Added lines #L583 - L1457 were not covered by tests
[warning] 1459-1459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1459
Added line #L1459 was not covered by tests
[warning] 1472-1504: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1472-L1504
Added lines #L1472 - L1504 were not covered by tests
[warning] 1506-1610: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1506-L1610
Added lines #L1506 - L1610 were not covered by tests
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx
[warning] 5-5: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L5
Added line #L5 was not covered by tests
[warning] 190-190: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L190
Added line #L190 was not covered by tests
[warning] 194-242: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L194-L242
Added lines #L194 - L242 were not covered by tests
⏰ Context from checks skipped due to timeout of 90000ms (7)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Deploy-Preview
- GitHub Check: Verify generated types
- GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)
881-885
: Confirmwas_resumed
is part ofSessionWithRelations
.
calculateFocusScore
accessessession.was_resumed
, but that property was previously missing in the public type and triggered CI errors (#prior review).
If the type definition in../types
has not been updated accordingly this line will still break the build.Please verify that
SessionWithRelations
now includes:interface SessionWithRelations { … was_resumed?: boolean; }and that the backend populates it.
Otherwise add a runtime guard or extend the local type alias.
…pdate analytics view
Add assignee information to tasks API with is_assigned_to_current_user flag
Implement smart task sorting: assigned tasks → priority → creation date
Add 'My Tasks' and 'Unassigned' filters with count badges
Add filter persistence using localStorage with workspace-specific keys
Enhance task cards with assignee avatars and assignment status
Add visual indicators for user-assigned tasks (blue styling)
Implement active filter chips with individual remove functionality
Add feature parity between sidebar and timer controls filtering"
Added new heatmaps:

Hybrid year + calendar

Calendar only

Compact cards

Continue last session

Next task

Analytics

Stopwatch



Pomodoro


Custom = Enhanced stopwatch + countdown




Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores